-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require 'mc.class'
local log = require 'mc.logging'
local mcu_object_base = require 'mcu.mcu_object'
local subcomp = require 'mcu.upgrade.sub_component'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local cmn = require 'common'
local file_sec = require 'utils.file'
local utils = require 'mc.utils'
local json = require 'cjson'
local skynet = require 'skynet'
local utils_core = require 'utils.core'
local vos = require 'utils.vos'
local fructl = require 'mcu.upgrade.fructl_handler'
local gen_hw_bus = require 'general_hardware_bus'

local smc_mcu_object = class(mcu_object_base)

local VRD_DUMP_FILE_PATH<const> = '/data/var/log/vrd/'

function smc_mcu_object:init()
    self.LibMaxSize = 0
    self.BinMaxSize = 0
    self.vrd_dump = {}
    self:get_vrd_log_in_init()
    self.mcu_log_type_record = {}
    local ok, err = pcall(self.collect_mcu_dump_info_task, self)
    if not ok then
        log:error('collect mcu dump info task failed, err:%s', err)
    end
end

function smc_mcu_object:set_lib_max_size(lib_max_size)
    self.LibMaxSize = lib_max_size
end

function smc_mcu_object:set_bin_max_size(bin_max_size)
    self.BinMaxSize = bin_max_size
end

function smc_mcu_object:get_lib_max_size()
    return self.LibMaxSize
end

function smc_mcu_object:get_bin_max_size()
    return self.BinMaxSize
end

function smc_mcu_object:get_firmeware_info(index)
    return self.interface:get_upgrade_detail(index)
end
 
--获取MCU支持的日志类型
function smc_mcu_object:get_mcu_type_list()
    local uid = self:get_uid()
    log:debug('query %s type list', string.format('MCU_%s', uid))
    local ok, is_supported, type_data = pcall(function() return self.interface:get_type_list() end)

    --不支持收集日志
    if not ok or not is_supported or not type_data then
        log:debug('%s is not support to collect log', string.format('MCU_%s', uid))
        return false, {}
    end

    --smc定义bit 1~31代表日志类型， 逐个bit位判断是否为1
    --rsp:byte转出来的结果是单字节数据
    local type = ''
    local type_list = {}
    local index, offset, data
    for i = 1, 31 do
        index = i // 8 + 1
        offset = i % 8
        data = 1 << offset
        if type_data[index] & data ~= 0 and MCU_ENUMS.MCU_LOG_BIT_TO_TYPE[i] then
            type = MCU_ENUMS.MCU_LOG_BIT_TO_TYPE[i]
            table.insert(type_list, type)
        end
    end
    return true, type_list
end

--获取MCU日志
--num_total_frames: 单个type类型的总帧数， U8
--byte_pre_frame:单帧长度， U8
--format：日志格式(1:reg, 2:txt)
function smc_mcu_object:get_mcu_log(uid, type)
    --查询日志信息
    local mcu_uid = string.format('MCU_%s', uid)
    log:debug('get %s %s log info', mcu_uid, type)
    local type_info = self.interface:get_mcu_type_info(MCU_ENUMS.MCU_LOG_TYPE_TO_BIT[type])
    if not type_info then
        log:error('get mcu type info failed, err:%s', type_info)
        return nil, nil
    end
    --收集日志
    log:debug('collect %s %s log', mcu_uid, type)
    local log_data = self.interface:get_single_frame(type_info)
    --获取失败，返回空字符串
    if not log_data then
        log:error('get single frame failed, err:%s', log_data)
        return nil, nil
    end
    return log_data, type_info['format']
end

--文件最大200k
local MAX_LOG_SIZE<const> = 200 * 1024
--仅转储一份， 不压缩
function smc_mcu_object:save_log_file(path, file_name)
    local dest_file_path = path .. '/' .. file_name .. '.1'
    local src_file_path = path .. '/' .. file_name
    local ok, ret = pcall(file_sec.move_file_s, src_file_path, dest_file_path)
    if not (ok and ret == 0) then
        log:error('execute mv failed, ret:%s', ret)
        return 
    end
    utils_core.chmod_s(dest_file_path, utils.S_IRUSR | utils.S_IRGRP)
end

--记录MCU日志文件
function smc_mcu_object:get_mcu_dump_info(type, path)
    local uid =  self:get_uid()
    local print_data = string.format('=======================MCU_%s_LOG Time:%s=================\n',
        uid, os.date("%Y%m%d%H%M%S", os.time()))
    local type_data, format = self:get_mcu_log(uid, type)
    if not type_data or not format then
        return 
    end
    print_data =  print_data .. type_data .. '\n'
    local file_name = string.format('%s_%s_dump', type, format)
    local file_path = string.format('%s/%s', path, file_name)
    --超过200k，触发转储
    if vos.get_file_accessible(file_path) and vos.get_file_length(file_path) > MAX_LOG_SIZE then
        self:save_log_file(path, file_name)
    end

    local file, err = file_sec.open_s(file_path, 'a+')
    if not file then
        log:error('open %s failed, uid:%s, err:%s', file_name, uid, err)
        return
    end
    local ok, rsp = pcall(file.write, file, print_data)
    if not ok then
        file:close()
        log:error('write %s failed, uid:%s, err:%s', file_name, uid, rsp)
        return
    end

    file:close()
    utils_core.chmod_s(file_path, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP)
    --清除这个类型的日志
    log:debug('clear %s %s log', string.format('MCU_%s', uid), type)
    self.interface:clear_mcu_log()
    log:debug('%s %s info collect finish', uid, file_name)
end

--默认例测时间30s
local BASE_LOOP_TIME<const> = 3000
--频繁置位时的特殊例测时间60mins
local SPEC_LOOP_TIME<const> = 360000
--三次查询的间隔， ms单位
local DIFF_TIME<const> = 900000
--查询type失败时固定例测时间为10min
local MAX_TIME<const> = 60000

--计算例测时间
function smc_mcu_object:get_loop_time()
    local loop_time = BASE_LOOP_TIME
    --获取系统时间同步状态， 记录一次
    local new_timestamp = vos.vos_tick_get()
    table.insert(self.mcu_log_type_record, 1, new_timestamp)
    --如果长度为3，则代表该mcu_type被访问了3次
    if #self.mcu_log_type_record == 3 then
        local time_diff = self.mcu_log_type_record[1] - self.mcu_log_type_record[3]
        if math.abs(time_diff) < DIFF_TIME then
            -- 三次置位在15min内， 增加例测时间到60min， 清空数据
            log:debug('collect log 3 times in 15min, modify loop time to 60 min')
            self.mcu_log_type_record = {}
            loop_time = SPEC_LOOP_TIME
        else
            --三次置位在15min外，无异常， 移除最后一次记录的数据
            log:debug('collect log 3 times not in 15min, clear the last tick record')
            table.remove(self.mcu_log_type_record)
        end
    else
        --未满3次， 可能一次记录或者已经触发频繁置位，恢复例测时间
        loop_time = BASE_LOOP_TIME
    end
    return loop_time
end

--MCU日志通用收集
function smc_mcu_object:collect_mcu_dump_info_task()
    local loop_time = BASE_LOOP_TIME
    local is_supported = false
    local type_list = {}
    local ok, uid, power_state
    --目录不存在时， 创建目录来收集日志
    if not vos.get_file_accessible(MCU_ENUMS.MCU_LOG_PATH) then
        utils_core.mkdir_with_parents(MCU_ENUMS.MCU_LOG_PATH, utils.S_IRWXU | utils.S_IRGRP | utils.S_IXGRP)
    end
    --遍历所有MCU对象，执行对应的收集函数
    -- 不同MCU需要收集的日志类型不同， 要分开保存
    skynet.fork_loop({count = 0}, function()
        power_state = fructl.get_power_status(gen_hw_bus.get_instance().bus, 1)
        if power_state == 'OFF' then
            log:debug('the power state is not support to collect log, power_state = %s', power_state)
            goto continue
        end
        ok, is_supported, type_list = pcall(function() return self:get_mcu_type_list() end)
        --查询type失败，例测时间修改到10min
        if not ok or not is_supported then
            loop_time = MAX_TIME
            goto continue
        end
        --支持收集， 但是没有type置位
        if #type_list == 0 then
            uid = self:get_uid()
            log:debug('%s support collect log, but type list is empty', string.format('MCU_%s', uid))
            goto continue
        end
        --查询成功，恢复默认例测时间
        loop_time = BASE_LOOP_TIME
        for _, type in ipairs(type_list) do
            loop_time = self:get_loop_time()
            self:get_mcu_dump_info(type, MCU_ENUMS.MCU_LOG_PATH)
        end
        ::continue::
        skynet.sleep(loop_time)
    end)
end

local function get_per_vrd_dump_data(mcu_id, vrd_dump, total_count)
    local vrd_data = {}
    for i = 1, #vrd_dump // total_count do
        local vrd_num = 'Vrd' .. string.format("%-3s", i)
        local value = {}
        for j = 1, total_count do
            value[j] = ' ' .. string.format('%02X', vrd_dump[(i - 1) * total_count + j])
        end
        local data
        local ok,err = pcall(function()
            data = string.format('%-20s | %-4s | %s\n', mcu_id, vrd_num, table.concat(value))
        end)

        if not ok then
            log:error('string.format error: %s', err)
            data = ''
        end
        vrd_data[i] = data
    end
    vrd_data = table.concat(vrd_data)
    return vrd_data
end

function smc_mcu_object:get_vrd_log_in_init()
    local ok, file_data = pcall(function()
        return self:get_vrd_log()
    end)
    if not ok then
        log:error('get vrd log error: %s', file_data)
    end
    if file_data then
        for _, data in pairs(file_data) do
            table.insert(self.vrd_dump, data)
        end
    end
end

function smc_mcu_object:get_vrd_log()
    local file_name = VRD_DUMP_FILE_PATH .. self.mcu.Id
    local file, err = file_sec.open_s(file_name, "r")
    if not file then
        log:error('open %s failed, err: %s', file_name, err)
        return
    end
    local print_data = utils.close(file, pcall(file.read, file, 'a'))
    if print_data == '' then
        log:error('failed to read data from %s', file_name)
        return
    else
        log:info('Data read from file: %s', print_data)
    end

    return json.decode(print_data)
end

local MAX_DUMP_NUM = 10
function smc_mcu_object:get_vrd_dump_info(mcu_id)
    local vrd_dump_per_time = ''
    local sec = os.time()
    local time = os.date('log time:%Y-%m-%d %H:%M:%S', sec)

    vrd_dump_per_time = vrd_dump_per_time .. time .. '\n'
    local head
    head = string.format('%-20s | %-4s | %s\n', 'Id', 'VrdNum', 'Value')
    vrd_dump_per_time = vrd_dump_per_time .. head
    local total_dump_info , total_count = self.interface:get_vrd_dump()
    if total_dump_info and #total_dump_info ~= 0 then
        vrd_dump_per_time = vrd_dump_per_time .. get_per_vrd_dump_data(mcu_id, total_dump_info, total_count) .. '\n'
    end
    
    if self.vrd_dump and #self.vrd_dump == MAX_DUMP_NUM then
        table.remove(self.vrd_dump,1)
    end
    if not self.vrd_dump then 
        self.vrd_dump = {}
    end
    table.insert(self.vrd_dump, vrd_dump_per_time)

    utils.mkdir_with_parents(VRD_DUMP_FILE_PATH, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP)
    local file_name = VRD_DUMP_FILE_PATH .. self.mcu.Id
    local file, err = file_sec.open_s(file_name, 'wb+')
    if not file then
        log:error('open vrd dump file failed, err: %s', err)
        return
    end
    utils.close(file, pcall(file.write, file, json.encode(self.vrd_dump)))
    return
end

function smc_mcu_object:register_firmware_inventory(bus)
    -- 查询子组件数量
    local cnt, lib_max_size, bin_max_size = self.interface:get_upgrade_cnt()
    log:notice('[McuUpgrade]get smc firmware cnt: %s', cnt)
    if not cnt or not lib_max_size or not bin_max_size then
        log:warn('get SMC sub-component count fail')
    end

    self:set_lib_max_size(lib_max_size)
    self:set_bin_max_size(bin_max_size)
    if cnt then
        -- 索引从0开始,查询子件详细信息并向FirmwareInventory注册, 不包括MCU和VRD子组件
        self.sub_component_info_list = {}
        for i = 0, cnt - 1 do
            local detail = self.interface:get_upgrade_detail(i)
            if not detail then
                goto continue
            end
            table.insert(self.sub_component_info_list, detail)
            local component = subcomp.new(i, detail)

            if not component or next(component) == nil then
                goto continue
            end

            component:set_ref_mcu(self)
            self.super.insert_subcomp_detail(self, component)
            -- 将支持的MCU子件对象注册到Firmware
            if component.Type ~= MCU_ENUMS.SUB_COMPONENT_TYPE.T_MCU and
                component.Type ~= MCU_ENUMS.SUB_COMPONENT_TYPE.T_VRD and
                MCU_ENUMS.SUB_COMPONENT_TYPE_TABLE[component.Type] then
                component:register_component_firmware_info(bus)
            end

            ::continue::
        end
        self.vrd_info_changed:emit(self.sub_component_info_list)
    end
    -- 将vrd子件对象注册到Firmware
    subcomp.register_vrd_firmware_info(self, bus)

    local component = self.mcu.SubCompList[1]
    if component and next(component) ~= nil then
        if component.Type ~= MCU_ENUMS.SUB_COMPONENT_TYPE.T_MCU or component.No ~= 0 then
            log:warn('the first sub-component is not MCU')
        else
            self.super.set_mcu_major_version(self, component.MajorVersion)
            self.super.set_mcu_minor_version(self, component.MinorVersion)
            self.super.set_mcu_revision(self, component.Revision)
            self.super.register_mcu_firmware_inventory(self, bus)
        end
    end
    self.vrd_load = true
    log:notice('[McuUpgrade] (%s)register SMC version to FirmwareInventory done', self.mcu.BoardType)
end

return smc_mcu_object